//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.IO;
using System.Net;
using System.Text;

namespace OpenLS.Vml.Parsing
{
    internal class Entity
    {
        public const Char Eof = (char) 65535;

        private int _absolutePos;

        private TextReader _textReader;
        internal bool IsWhitespace;
        public char Lastchar;
        private int _line;
        private string Literal;
        public Entity Parent;

        public Entity(TextReader textReader)
        {
            _textReader = textReader;
        }

        public char ReadChar()
        {
            var ch = (char) _textReader.Read();
            if (ch == 0)
            {
                ch = ' ';
            }
            _absolutePos++;
            if (ch == 0xa)
            {
                IsWhitespace = true;
                _line++;
            }
            else if (ch == ' ' || ch == '\t')
            {
                IsWhitespace = true;
                if (Lastchar == 0xd)
                {
                    _line++;
                }
            }
            else if (ch == 0xd)
            {
                IsWhitespace = true;
            }
            else
            {
                IsWhitespace = false;
                if (Lastchar == 0xd)
                {
                    _line++;
                }
            }
            Lastchar = ch;
            return ch;
        }

        public void Open(Entity parent)
        {
            Parent = parent;
            _line = 1;
            if (Literal != null)
                _textReader = new StringReader(Literal);
        }

        public void Close()
        {
        }

        public char SkipWhitespace()
        {
            char ch = Lastchar;
            while (ch != Eof && (ch == ' ' || ch == '\r' || ch == '\n' || ch == '\t'))
            {
                ch = ReadChar();
            }
            return ch;
        }

        public string ScanToken(StringBuilder sb, string term, bool nmtoken)
        {
            sb.Length = 0;
            char ch = Lastchar;
            if (nmtoken && ch != '_' && !Char.IsLetter(ch))
            {
                throw new Exception(
                    String.Format("Invalid name start character '{0}'", ch));
            }
            while (ch != Eof && term.IndexOf(ch) < 0)
            {
                if (!nmtoken || ch == '_' || ch == '.' || ch == '-' || ch == ':' || Char.IsLetterOrDigit(ch))
                {
                    sb.Append(ch);
                }
                else
                {
                    throw new Exception(
                        String.Format("Invalid name character '{0}'", ch));
                }
                ch = ReadChar();
            }
            return sb.ToString();
        }

        public string ScanLiteral(StringBuilder sb, char quote)
        {
            sb.Length = 0;
            char ch = ReadChar();
            while (ch != Eof && ch != quote)
            {
                if (ch == '&')
                {
                    ch = ReadChar();
                    if (ch == '#')
                    {
                        string charent = ExpandCharEntity();
                        sb.Append(charent);
                        ch = Lastchar;
                    }
                    else
                    {
                        sb.Append('&');
                        sb.Append(ch);
                        ch = ReadChar();
                    }
                }
                else
                {
                    sb.Append(ch);
                    ch = ReadChar();
                }
            }
            ReadChar(); // consume end quote.           
            return sb.ToString();
        }

        public string ScanToEnd(StringBuilder sb, string type, string terminators)
        {
            if (sb != null) sb.Length = 0;
            int start = _line;
            char ch = ReadChar();
            int state = 0;
            char next = terminators[state];
            while (ch != Eof)
            {
                if (ch == next)
                {
                    state++;
                    if (state >= terminators.Length)
                    {
                        break;
                    }
                    next = terminators[state];
                }
                else if (state > 0)
                {
                    int i = state - 1;
                    int newstate = 0;
                    while (i >= 0 && newstate == 0)
                    {
                        if (terminators[i] == ch)
                        {
                            int j = 1;
                            while (i - j >= 0)
                            {
                                if (terminators[i - j] != terminators[state - j])
                                    break;
                                j++;
                            }
                            if (j > i)
                            {
                                newstate = i + 1;
                            }
                        }
                        else
                        {
                            i--;
                        }
                    }
                    if (sb != null)
                    {
                        i = (i < 0) ? 1 : 0;
                        for (int k = 0; k <= state - newstate - i; k++)
                        {
                            sb.Append(terminators[k]);
                        }
                        if (i > 0)
                            sb.Append(ch);
                    }
                    state = newstate;
                    next = terminators[newstate];
                }
                else
                {
                    if (sb != null) sb.Append(ch);
                }
                ch = ReadChar();
            }
            if (ch == 0) Error(type + " starting on line {0} was never closed", start);
            ReadChar(); // consume last char in termination sequence.
            if (sb != null) return sb.ToString();
            return string.Empty;
        }

        public string ExpandCharEntity()
        {
            char ch = ReadChar();
            int v = 0;
            if (ch == 'x')
            {
                ch = ReadChar();
                for (; ch != Eof && ch != ';'; ch = ReadChar())
                {
                    int p = 0;
                    if (ch >= '0' && ch <= '9')
                    {
                        p = ch - '0';
                    }
                    else if (ch >= 'a' && ch <= 'f')
                    {
                        p = ch - 'a' + 10;
                    }
                    else if (ch >= 'A' && ch <= 'F')
                    {
                        p = ch - 'A' + 10;
                    }
                    else
                    {
                        break; 
                    }
                    v = (v*16) + p;
                }
            }
            else
            {
                for (; ch != Eof && ch != ';'; ch = ReadChar())
                {
                    if (ch >= '0' && ch <= '9')
                    {
                        v = (v*10) + (ch - '0');
                    }
                    else
                    {
                        break; 
                    }
                }
            }
            if (ch == 0)
            {
                error("Premature {0} parsing entity reference", ch);
            }
            else if (ch == ';')
            {
                ReadChar();
            }
            return Convert.ToChar(v).ToString();
        }

        private static void error(string msg, char ch)
        {
            string str = (ch == Eof) ? "EOF" : Char.ToString(ch);
            throw new Exception(String.Format(msg, str));
        }

        private static void Error(string msg, int x)
        {
            throw new Exception(String.Format(msg, x));
        }
    }
}